class Vue {
  constructor(options) {
    this.$options = options;
    this.$data =
      typeof options.data == "function"
        ? options.data.call(this)
        : options.data;

    // data响应式处理
    observe(this.$data);

    // 代理this.$data
    proxy(this);

    if (options.el) {
      this.$mount(options.el);
    }
  }

  $mount(el) {
    this.$el = document.querySelector(el);

    // 组件更新函数
    const updateComponent = () => {
      // vnode版
      const vnode = this.$options.render.call(this, this.$createElement);
      this._update(vnode);
    };

    // 创建组件watcher
    new Watcher(this, updateComponent);
  }
  $createElement(tag, data, children) {
    return { tag, data, children };
  }
  _update(vnode) {
    const preVnode = this._vnode;
    if (!preVnode) {
      // init
      this.__patch__(this.$el, vnode);
    } else {
      this.__patch__(preVnode, vnode);
    }
    // 缓存当前生成的vnode,便于下次更新时进行比对
    this._vnode = vnode;
  }
  __patch__(oldVnode, vnode) {
    // 首次进来oldVnode为空
    if (oldVnode.nodeType) {
      const el = this.createElm(vnode);
      const parent = oldVnode.parentElement;
      const refElm = oldVnode.nextSibling;
      parent.insertBefore(el, refElm);
      parent.removeChild(this.$el);
    } else {
      // update
      const el = (vnode.el = oldVnode.el);
      if (oldVnode.tag == vnode.tag) {
        // 相同标签
        const oldCh = oldVnode.children;
        const newCh = vnode.children;
        if (typeof newCh == "string") {
          if (typeof oldCh == "string") {
            // 文本替换
            if (newCh != oldCh) el.textContent = newCh;
          } else {
            el.textContent = newCh;
          }
        } else {
          if (oldCh == "string") {
            el.innerHTML = "";
            newCh.forEach((child) => el.appendChild(this.createElm(child)));
          } else {
            this.updateChildren(el, oldCh, newCh);
          }
        }
      } else {
      }
    }
  }
  createElm(vnode) {
    console.log("createElm", vnode.tag);
    // 递归创建整颗dom树
    const el = document.createElement(vnode.tag);
    // prop
    // children
    if (vnode.children) {
      if (typeof vnode.children == "string") {
        el.textContent = vnode.children;
      } else {
        vnode.children.forEach((n) => {
          el.appendChild(this.createElm(n));
        });
      }
    }
    // 保存vnode与dom的对应关系
    vnode.el = el;
    return el;
  }
  // diff -- 暴力法
  updateChildren(parentElm, oldCh, newCh) {
    // 这里暂且直接patch对应索引的两个节点
    const len = Math.min(oldCh.length, newCh.length);
    for (let i = 0; i < len; i++) this.__patch__(oldCh[i], newCh[i]);
    // newCh若是更长的那个，说明有新增
    if (newCh.length > oldCh.length) {
      newCh.slice(len).forEach((child) => {
        const el = this.createElm(child);
        parentElm.appendChild(el);
      });
    } else if (newCh.length < oldCh.length) {
      // oldCh若是更长的那个，说明有删减
      oldCh.slice(len).forEach((child) => {
        parentElm.removeChild(child.el);
      });
    }
  }
}
function proxy(vm) {
  Object.keys(vm.$data).forEach((key) => {
    if(key == '__ob__') return
    Object.defineProperty(vm, key, {
      get() {
        return vm.$data[key];
      },
      set(newVal) {
        vm.$data[key] = newVal;
      },
    });
  });
}

function observe(obj) {
  if (typeof obj != "object" || obj == null) return;

  if (obj.__ob__) return obj.__ob__;
  obj.__ob__ = new Observer(obj);
  return obj.__ob__;
}

function defineReactive(obj, key, value) {
  // 如果值是对象,则需要递归进行处理
  const childOb = observe(value);

  // 为当前key创建Dep对象,用于依赖管理
  const dep = new Dep();

  Object.defineProperty(obj, key, {
    get() {
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
        }
        // Array.isArray(value)
      }
      return value;
    },
    set(newVal) {
      console.log("set", newVal);
      if (newVal != value) {
        value = newVal;
        observe(value);
        dep.notify();
      }
    },
  });
}

// 创建基于Array.prototype的派生类
const arrayMethods = Object.create(Array.prototype);
// 拦截用于修改数组的7个方法
const methodsToPatch = ["push", "pop", "shift", "unshift", "splice", "reverse"];

// 覆盖原型方法，注入响应式处理逻辑
methodsToPatch.forEach((method) => {
  const origin = arrayMethods[method];
  Object.defineProperty(arrayMethods, method, {
    value: function mutator(...arg) {
      let ob = this.__ob__;
      const result = origin.apply(this, arg);
      let inserted;
      if (method == "push") inserted = arg;
      else if (method == "splice") inserted = arg.slice(2);
      // 有新元素添加，新元素也要进行响应式处理
      if (inserted) ob.observeArray(inserted);
      // 通知更新

      console.log("methodsToPatch", method);
      ob.dep.notify();
      return result;
    },
  });
});

// 数组和对象使用不同的操作
class Observer {
  constructor(value) {
    this.value = value;
    this.dep = new Dep();
    if (Array.isArray(value)) {
      // 将value的原型指向arrayMethods
      value.__proto__ = arrayMethods;
      // 对数组每项进行响应式处理
      this.observeArray(value);
    } else {
      this.walk(value);
    }
  }
  // 对象响应式处理
  walk(obj) {
    Object.keys(this.value).forEach((key) => {
      defineReactive(obj, key, obj[key]);
    });
  }
  // 数组响应式处理
  observeArray(value) {
    value.forEach((v) => observe(v));
  }
}

let uid = 0;
// 组件观察者
class Watcher {
  constructor(vm, updateFn) {
    this.vm = vm;
    this.id = ++uid;
    this.newDepIds = new Set(); // 
    this.depIds = new Set();
    this.newDeps = [];
    this.updateFn = updateFn;
    this.get();
  }
  // 收集依赖
  get() {
    Dep.target = this;
    this.updateFn.call(this.vm);
    Dep.target = null;
  }
  update() {
    this.get();
  }
  // 为一个指令添加依赖
  addDep(dep) {
    const id = dep.id;
    // 去重
    if (!this.newDepIds.has(id)) {
      // watcher添加它的dep关系
      this.newDepIds.add(id);
      this.newDeps.push(dep);
      if (!this.depIds.has(id)) {
        // 反过来，dep添加和watcher关系
        dep.addDep(this);
      }
    }
  }
}
let d_uid = 0;
// 依赖收集
class Dep {
  constructor() {
    this.subs = [];
    this.id = ++d_uid;
  }
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }
  addDep(w) {
    this.subs.push(w);
  }
  notify() {
    this.subs.forEach((w) => w.update());
  }
}
